fix: studio Vercel API routes returning HTML instead of JSON + CORS for temp domains#1052
fix: studio Vercel API routes returning HTML instead of JSON + CORS for temp domains#1052
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…tead of HTML - Add outputDirectory: "public" to vercel.json - Add functions block declaring api/index.js as serverless function - Fix API rewrite destination from /api to /api/index.js - Update CHANGELOG.md Agent-Logs-Url: https://github.com/objectstack-ai/spec/sessions/d0116abe-0010-46e2-a25d-08bc5236156b Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
|
@copilot vercel 部署使用临时域名时,报跨域错误 |
Add hono/cors middleware to the outer Hono app in server/index.ts so the serverless function returns proper Access-Control-Allow-Origin headers for cross-origin requests from Vercel preview/temporary domains. Allowed origins: - All Vercel deployment URLs from VERCEL_URL, VERCEL_BRANCH_URL, VERCEL_PROJECT_PRODUCTION_URL env vars - Any *.vercel.app subdomain (covers all preview deployments) - localhost for local development The CORS middleware is placed before the catch-all route so OPTIONS preflight requests are handled immediately without kernel boot latency. Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/324fd769-65e6-430a-aab8-06ad343e75f2 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
Added Hono CORS middleware to the outer app in Allowed origins:
The CORS middleware is placed on the outer Hono app before the catch-all route, so OPTIONS preflight requests are answered immediately without waiting for kernel cold-start. |
|
@copilot |
…ion error The `functions` block pattern `api/index.js` fails Vercel's pre-build validation because the file is generated during the build step (by bundle-api.mjs), not available beforehand. The in-code `export const config` in server/index.ts already configures memory and maxDuration — Vercel's @vercel/node runtime picks it up from the deployed function bundle without any vercel.json pattern matching. Also reverts the rewrite destination to `/api` (the auto-detected route for api/index.js) instead of `/api/index.js` (a file path that doesn't map to a Vercel route). Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/16819ebf-4d21-4ae9-892d-8c1593b21504 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Removed the The function config (memory/maxDuration) is already handled by the in-code Also reverted the rewrite destination to |
… Vercel TS compilation and file-not-found errors Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/656a5b4f-383c-4318-a657-a952f4445583 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
Fixed in This avoids the chicken-and-egg problem:
|
|
还是不对,能不能认真参考 https://github.com/objectstack-ai/hotcrm/blob/main/vercel.json Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/var/task/apps/studio/server/index' imported from /var/task/apps/studio/api/[[...route]].js |
|
@copilot Error: Dynamic require of "events" is not supported |
…-in require() support Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/3888d7e6-570c-478b-b969-84043c288a94 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
Fixed in import { createRequire } from "module";
const require = createRequire(import.meta.url);When esbuild converts CJS dependencies (knex/tarn) to ESM format, their
The The follow-up "Dynamic require of events" error (from The studio uses esbuild bundling (instead of hotcrm's native Vercel TS compilation) because workspace packages at |
|
@copilot 2026-04-02 07:46:05.148 [info] [Vercel] GET http://objectstack-play-git-copilot-fix-api-routes-d603fc-object-stack.vercel.app/api/v1/meta?path=v1%2Fmeta
|
…ibsql/client) on Vercel Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/fbeefcaa-1bf8-418e-aed0-baafcf1b18aa Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
Fixed in |
|
@copilot [Vercel] Kernel boot failed: Knex: run
|
…ict mode on Vercel Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/5da7c570-c9a4-4a8d-93d4-bcb11dad0da9 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
Fixed in Added a build step in |
All
/api/*routes on the Vercel-deployed studio return the SPA'sindex.htmlinstead of JSON because Vercel doesn't detect the serverless function. Additionally, when deployed with a temporary/preview Vercel domain, cross-origin API requests fail with CORS errors becauseVITE_SERVER_URLwas hardcoded to the production domain, forcing all deployments to call the production API cross-origin.Root cause
Six issues prevented the serverless function from working:
No committed API route file — Vercel needs a file in the
api/directory to exist pre-build for function detection. The previousapi/index.jswas only generated during the build step bybundle-api.mjs, so Vercel never recognised it as a serverless function.CJS/ESM format mismatch — The esbuild bundle used
format: 'cjs'(CommonJS), but the studio'spackage.jsonhas"type": "module". Node.js treats.jsfiles as ESM intype: moduleprojects, so the CJS output (require()/module.exports) would fail to parse at runtime.Vercel TypeScript compilation conflicts — Committing a
.tsstub inapi/caused a chicken-and-egg problem: if the.tsexists, Vercel's@vercel/nodebuilder compiles it and overwrites the self-contained esbuild bundle with a thin re-export referencing../server/index(which doesn't exist at runtime →ERR_MODULE_NOT_FOUND). Deleting the.tsduring build causes Vercel to error with "File not found" since it expects pre-build-detected files to still exist post-build.CJS→ESM dynamic require of Node.js built-ins — When esbuild converts CJS dependencies (
knex/tarn) to ESM format, theirrequire('events')calls are replaced by esbuild's__requireshim, which throws"Dynamic require of 'events' is not supported"for Node.js built-in modules.Missing native addons at runtime — Native C++ modules (
better-sqlite3,@libsql/client) are marked asexternalin esbuild (they can't be bundled), but withoutfunctions.includeFilesinvercel.json, Vercel doesn't package them into the deployed serverless function →Cannot find module 'better-sqlite3'.pnpm strict mode hides native modules from
includeFiles— Even withincludeFilesconfigured, pnpm's strict isolation (noshamefully-hoist) stores native modules in the monorepo rootnode_modules/.pnpm/store, not in the app-levelnode_modules/. Since Vercel'sincludeFilesresolves relative to the function's directory (apps/studio/), the globnode_modules/better-sqlite3/**matches nothing — the modules physically live at../../node_modules/.pnpm/better-sqlite3@*/node_modules/better-sqlite3/.Solution — committed
.jswrapper + separate esbuild bundle withcreateRequire+includeFiles+ build-time module copyapps/studio/api/[[...route]].js(new).jscatch-all API route (not.ts) so Vercel detects it pre-build without triggering TypeScript compilation./_handler.js— the self-contained esbuild bundle generated during build.jswrapper avoids all three failure modes: the file exists for pre-build detection, is never deleted during build, and Vercel does not TS-compile itapps/studio/scripts/bundle-api.mjsformatfrom'cjs'to'esm'— fixes the"type": "module"compatibility issueoutfilefrom'api/index.js'to'api/_handler.js'— outputs to a separate file (prefixed with_so Vercel treats it as a helper file, not a separate function) that the committed wrapper re-exportscreateRequirebanner — provides a realrequirefunction in the ESM scope so that esbuild's__requireshim (which checkstypeof require !== "undefined") uses it instead of throwing for Node.js built-in modules likeevents,stream,path, etc. required by CJS dependencies (knex/tarn)apps/studio/scripts/build-vercel.shapi/_handler.js(the self-contained bundle)better-sqlite3,@libsql/client) from the monorepo rootnode_modules/into the studio's localnode_modules/— this ensures Vercel'sincludeFilesglob can find them, since pnpm's strict mode doesn't symlink transitive native dependencies into app-levelnode_modules/.jswrapper and the generated_handler.jscoexist cleanlyapps/studio/vercel.jsonVITE_SERVER_URLfrom"https://play.objectstack.ai"to""(empty string / same-origin) so each deployment — including previews — calls its own serverless function instead of the production API cross-origin{ "source": "/api/(.*)", "destination": "/api" }to{ "source": "/api/:path*", "destination": "/api/[[...route]]" }matching the hotcrm patternoutputDirectory— Vercel's default forframework: nullalready serves static frompublic/and functions fromapi/at project rootfunctionsblock withincludeFilesfor native addons (better-sqlite3,@libsql/client) that esbuild marks as external and cannot bundle — these must be explicitly included in the Vercel function's deployment package, matching the same pattern used in hotcrm'svercel.json{ "build": { "env": { "VITE_RUNTIME_MODE": "server", "VITE_SERVER_URL": "" } }, "functions": { "api/**/*.js": { "memory": 1024, "maxDuration": 60, "includeFiles": "{node_modules/@libsql,node_modules/better-sqlite3}/**" } }, "rewrites": [ { "source": "/api/:path*", "destination": "/api/[[...route]]" }, { "source": "/((?!api/).*)", "destination": "/index.html" } ] }apps/studio/.gitignoreapidirectory to only ignoring build artifacts (api/_handler.js,api/_handler.js.map), so the committedapi/[[...route]].jswrapper is trackedapps/studio/server/index.tsgetVercelOrigins()helper that collects all Vercel deployment URLs from env vars (VERCEL_URL,VERCEL_BRANCH_URL,VERCEL_PROJECT_PRODUCTION_URL). Reused for both the CORS middleware and better-authtrustedOriginsto prevent allowlist drift.hono/corsmiddleware to the outer Hono app as a safety net for any remaining cross-origin scenarios (e.g., custom domain configurations)getVercelOrigins()*.vercel.appsubdomain (covers all preview/branch deployments)localhostfor local developmentcredentials: truefor authenticated cross-origin requests